unit smGUIPanel;

interface

uses
  SysUtils, smGUI, smCommonTypes, smUtils, smExceptions, smConst, Data.DBXJSON;

type
  TGUIPanel = class;
  TGUIControl = class;

  // GUI panel - child element of GUIManager, contains all the controls
  TGUIPanel = class(TGUIElement)
  protected
    var FCanFocus: Boolean;
    var FFixed: Boolean;
    var FDragStart: TGamePoint;
    var FClientRect: TGameRect;
    var FControls: TGameList;
    var FCaption: AnsiString;

    procedure OnMouseMove(const X, Y: Integer); override;
    procedure OnMouseDown(const Button: Byte); override;

    function GetControl(const Name: AnsiString): TGUIControl;
    procedure BringToFront();
    procedure ParseJSON(const Source: TJSONPair); override;
  public
    property CanFocus: Boolean read FCanFocus write FCanFocus;
    property Fixed: Boolean read FFixed write FFixed;
    property DragStart: TGamePoint read FDragStart;
    property ClientRect: TGameRect read FClientRect;
    property Controls[const Name: AnsiString]: TGUIControl read GetControl;
    property ControlList: TGameList read FControls;
    property Caption: AnsiString read FCaption write FCaption;

    function MouseOver(const MX, MY: Integer): Boolean; override;
    function AbsolutePos(): TGamePoint;
    procedure AddButton();
    procedure Perform(const Message: TGUIMessage; const Lo, Hi: Integer;
      var Handled: Boolean); override;
    procedure Clear();

    constructor Create(const AOwner: Pointer); override;
    destructor Destroy(); override;
  end;

  // Child element of panel control
  TGUIControl = class(TGUIElement)
  protected
    FOwner: TGUIPanel;

    procedure ParseJSON(const Source: TJSONPair); override;
  public
    property Owner: TGUIPanel read FOwner;
    function MouseOver(const MX, MY: Integer): Boolean; override;
    procedure Render(const ARenderer: Pointer); virtual; abstract;

    constructor Create(const APanel: TGUIPanel); reintroduce; virtual;
  end;

implementation

uses
  smMain, smGUIButton, smGUIRender;

{ TGUIPanel }

constructor TGUIPanel.Create;
begin
  inherited Create(AOwner);
  Owner.PanelList.Add(Self);
  FName := AnsiString(Format('panel%d', [Owner.PanelList.Count])); // generic name
  Caption := Name;

  ClientRect.SetValue(5, 15, Width - 10, Height - 20);

  FControls := TGameList.Create();
  FControls.ObjectElements := True;
end; 

function TGUIPanel.GetControl(const Name: AnsiString): TGUIControl;
var
  I: Integer;
begin
  for I := 0 to FControls.Count - 1 do
{$R-}
    if TGUIElement(FControls.Raw[I]).Name = Name then
    begin
      Result := FControls.Raw[I];
      Exit;
    end;
{$R+}
  Result := nil;
end;

function TGUIPanel.AbsolutePos: TGamePoint;
begin
  Result.X := Owner.ClientRect.Left + Left;
  Result.Y := Owner.ClientRect.Top + Top;
end;

function TGUIPanel.MouseOver(const MX, MY: Integer): Boolean;
begin
  Result := Enabled and Visible
      and (MX > Owner.ClientRect.Left + Left)
      and (MY > Owner.ClientRect.Top + Top)
      and (MX < Owner.ClientRect.Left + Left + Width)
      and (MY < Owner.ClientRect.Top + Top + Height);
end;

procedure TGUIPanel.OnMouseDown(const Button: Byte);
begin
  FDrag := Drag and not Fixed;
  // Move self to the top of the stack, coz we're in focus
  if Button = 0 then
    BringToFront();

  // Drag feature
  if FDrag then
  begin             // Mouse.Position - Self.Position
    FDragStart.X := Game.InputPlugin.MousePos.X - Left;
    FDragStart.Y := Game.InputPlugin.MousePos.Y - Top;
  end;
end;

procedure TGUIPanel.OnMouseMove(const X, Y: Integer);
const
  MagnetForce: Byte = 10;
begin
  // Drag&Drop feature
  if FDrag then
    with FPosition do
    begin     // Mouse.Position - Stored.Position
      Left := BoundI(X - FDragStart.X, 0, Owner.ClientRect.Width - Width);
      Top  := BoundI(Y - FDragStart.Y, 0, Owner.ClientRect.Height - Height);

      // Magnets :)
      if Left < MagnetForce then Left := 0;
      if Top  < MagnetForce then Top  := 0;
      if Left + Width > Owner.ClientRect.Width - MagnetForce then
        Left := Owner.ClientRect.Width - Width;
      if Top + Height > Owner.ClientRect.Height - MagnetForce then
        Top := Owner.ClientRect.Height - Height;
    end;
end;

procedure TGUIPanel.ParseJSON(const Source: TJSONPair);

  function LoadClientRect(const Source: TJSONObject): Boolean;
  var
    I: Integer;
  begin
    for I := 0 to Source.Size - 1 do
      with Source.Get(I) do
        if JsonString.Value = 'left' then
          Self.FClientRect.Left := TJSONNumber(JsonValue).AsInt

        else if JsonString.Value = 'top' then
          Self.FClientRect.Top := TJSONNumber(JsonValue).AsInt

        else if JsonString.Value = 'width' then
          Self.FClientRect.Width := TJSONNumber(JsonValue).AsInt

        else if JsonString.Value = 'height' then
          Self.FClientRect.Height := TJSONNumber(JsonValue).AsInt

        else if JsonString.Value = 'right' then
          Self.FClientRect.SetRight(TJSONNumber(JsonValue).AsInt)

        else if JsonString.Value = 'bottom' then
          Self.FClientRect.SetBottom(TJSONNumber(JsonValue).AsInt);

    Result := True;
  end;

  function LoadButton(const Source: TJSONObject): Boolean;
  var
    TempButton: TGUIButton;
  begin
    TempButton := TGUIButton.Create(Self);
    Result := TempButton.LoadFromJSON(Source as TJSONObject);

    if not Result then
      FControls.RemoveRelease(TempButton);
  end;

  function LoadHideButton(const Source: TJSONObject): Boolean;
  var
    TempButton: TGUIHideButton;
  begin
    TempButton := TGUIHideButton.Create(Self);
    Result := TempButton.LoadFromJSON(Source as TJSONObject);

    if not Result then
      FControls.RemoveRelease(TempButton);
  end;

begin
  inherited;

  with Source, Source.JsonString do
    if Value = 'can_focus' then
      FCanFocus := TJSONBoolean(JsonValue).AsBoolean

    else if Value = 'fixed' then
      FFixed := TJSONBoolean(JsonValue).AsBoolean

    else if Value = 'caption' then
      Caption := AnsiString(JsonValue.Value)

    else if Value = 'client_rect' then
    begin
      if not LoadClientRect(JsonValue as TJSONObject) then
        raise EGUILoadingParamFailed.Create(
          Value, JsonValue.Value
        );
    end

    else if Value = 'button' then
    begin
      if not LoadButton(JsonValue as TJSONObject) then
        raise EGUILoadingParamFailed.Create(
          Value, JsonValue.Value
        );
    end

    else if Value = 'hidebutton' then
    begin
      if not LoadHideButton(JsonValue as TJSONObject) then
        raise EGUILoadingParamFailed.Create(
          Value, JsonValue.Value
        );
    end;

end;

procedure TGUIPanel.Perform;
var
  I: Integer;
  OwnerHandled: Boolean;
begin
  OwnerHandled := Handled;

  if Enabled and Visible then
    for I := ControlList.Count - 1 downto 0 do
      TGUIControl(ControlList[I]).Perform(Message, Lo, Hi, Handled);

  // One of the children elements reacted on message
  if not OwnerHandled and Handled then
  begin
    State := gesHovered;
    if Message = gmMouseDown then
      BringToFront();
  end
  else
    inherited Perform(Message, Lo, Hi, Handled);
end;

procedure TGUIPanel.AddButton;
begin
  TGUIButton.Create(Self);
end;

procedure TGUIPanel.BringToFront;
begin
  Owner.PanelList.Pop(Self);
end;

procedure TGUIPanel.Clear;
begin
  FControls.Clear();
end;

destructor TGUIPanel.Destroy;
begin
  FControls.Destroy();
  inherited;
end;

{ TGUIControl }

constructor TGUIControl.Create(const APanel: TGUIPanel);
begin
  FOwner := APanel;
  inherited Create(Owner.Owner);
  Position.SetValue(0, 0, DefaultGUIControlWidth, DefaultGUIControlHeight);
  Owner.ControlList.Add(Self);
  Name := AnsiString(Format(
      '%s.control%d', [String(Owner.Name), Owner.ControlList.Count]
    ));
  FFont := Owner.Font;
end;

function TGUIControl.MouseOver(const MX, MY: Integer): Boolean;
begin
  with Owner.AbsolutePos do
    Result := Enabled and Visible
        and (MX > X + Owner.ClientRect.Left + Left)
        and (MY > Y + Owner.ClientRect.Top + Top)
        and (MX < X + Owner.ClientRect.Left + Left + Width)
        and (MY < Y + Owner.ClientRect.Top + Top + Height);
end;

procedure TGUIControl.ParseJSON(const Source: TJSONPair);
begin
  with Source, Source.JsonString do
    if Value = 'right' then
      FPosition.SetRight(
        Owner.ClientRect.Width - TJSONNumber(JsonValue).AsInt
      )

    else if Value = 'bottom' then
      FPosition.SetBottom(
        Owner.ClientRect.Height - TJSONNumber(JsonValue).AsInt
      )

    else inherited;
end;

end.
